#ifndef __LOG__HPP__
#define __LOG__HPP__

#include <iostream>
#include <string>
#include <memory>
#include <fstream>
#include <sstream>
#include <filesystem> // c++17
#include <ctime>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include "mutex.hpp"

namespace MyLog
{
    using namespace MutexModule;

#define gap "\r\n"
    // 策略模式——刷新策略
    // 虚基类
    class logstrategy
    {
    public:
        ~logstrategy() = default;
        virtual void synclog(const std::string &message) = 0;
    };

    // 刷新策略1--->向显示器刷新
    class consolelogstrategy : public logstrategy
    {
    public:
        ~consolelogstrategy() {}
        void synclog(const std::string &message) override
        {
            // 向显示器刷新需要加锁
            mutexguard lock(_mutex);
            std::cout << message << gap;
        }

    private:
        Mutex _mutex;
    };

    // 刷新策略2--->向指定文件里刷新
    const std::string defaultPath = "/var/log";
    const std::string defaultName = "log.log";

    class filelogstrategy : public logstrategy
    {
    public:
        filelogstrategy(const std::string &path = defaultPath, const std::string &name = defaultName)
            : _path(path),
              _file(name)
        {
            // 指定路径存在，直接返回；不存在，创建路径
            if (std::filesystem::exists(_path))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        ~filelogstrategy() {}

        void synclog(const std::string &message) override
        {
            // 向指定文件里打印, 向指定文件里面打印也得是原子的，得加锁
            mutexguard lock(_mutex);

            // 拼接路径+文件名
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;

            // 向指定文件里面以追加方式写入
            std::ofstream out(filename, std::ios::app);
            out << message << gap;

            out.close();
        }

    private:
        std::string _path;
        std::string _file;
        Mutex _mutex;
    };

    // 有了刷新策略接下来就是构造日志内容 --- 借助内部类来实现
    // [time] [日志等级] [进程pid] [文件] [行号] - 消息内容

    // 获取时间
    std::string GetTime()
    {
        // 1.获取当前的时间戳
        time_t cur_time = time(nullptr);

        // 2.将时间戳转化为年月日-时分秒
        struct tm format_time;
        localtime_r(&cur_time, &format_time);

        char time_buffer[128] = {0};
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%02d:%02d",
                 format_time.tm_year + 1900,
                 format_time.tm_mon + 1,
                 format_time.tm_mday,
                 format_time.tm_hour,
                 format_time.tm_min,
                 format_time.tm_sec);

        return time_buffer;
    }

    // 日志等级
    enum class loglevel
    {
        DEBUG,
        INFO,
        WARINING,
        ERROR,
        FATAL
    };

    // 获取日志等级
    std::string loglevelToString(loglevel level)
    {
        switch (level)
        {
        case loglevel::DEBUG:
            return "DEBUG";
        case loglevel::INFO:
            return "INFO";
        case loglevel::WARINING:
            return "WARNING";
        case loglevel::ERROR:
            return "ERROR";
        case loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOEN";
        }
    }

    class logger
    {
    public:
        logger()
        {
            // 默认使用显示器刷新策略
            UseConsoleStrategy();
        }
        ~logger() {}

        void UseConsoleStrategy() { _fflush_strategy = std::make_unique<consolelogstrategy>(); }
        void UseFileLogStrategy() { _fflush_strategy = std::make_unique<filelogstrategy>(); }

    private:
        std::unique_ptr<logstrategy> _fflush_strategy;

    public:
        // 内部类
        // 用来描述日志具体内容
        class logmessage
        {
        public:
            logmessage(loglevel &level, const std::string &name, int number, logger &logger)
                : _cur_time(GetTime()),
                  _log_level(level),
                  _file(name),
                  _line_number(number),
                  _pid(getpid()),
                  _logger(logger)
            {
                // 将格式化信息写入ss字符串流中
                std::stringstream ss;
                ss << "[" << _cur_time << "]"
                   << "[" << loglevelToString(_log_level) << "]"
                   << "[" << _pid << "]"
                   << "[" << _file << "]"
                   << "[" << _line_number << "]"
                   << "- ";

                // 从字符串中获取字符串
                _format_info = ss.str();
            }

            ~logmessage()
            {
                // 如果有刷新策略，在对象析构的时候进行刷新
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->synclog(_format_info);
                }
            }

            // 日志的主要内容
            template <typename T>
            logmessage &operator<<(const T &message)
            {
                std::stringstream ss;
                ss << message;
                _format_info += ss.str();
                return *this;
            }

        private:
            std::string _cur_time;
            loglevel _log_level;
            pid_t _pid;
            std::string _file;
            int _line_number;
            std::string _format_info;
            logger &_logger;
        }; // end of logmessage 内部类，用来处理日志的格式化内容以及主要内容

    // logger类内成员
    public:
        logmessage operator()(loglevel level, const std::string &file, int line)
        {
            return logmessage(level, file, line, *this);
        }

    }; // end of logger


    // 在命名空间中，有一个全局的logger对象
    // 调用日志方式：Glogger(loglevel, filename, linenumber) << "xxx" << "xxx" << ...;
    logger Glogger;

    // 使用宏，简化使用日志的方式
    #define LOG(level) Glogger(level, __FILE__, __LINE__)
    #define USE_CONSOLE_STARATEGY() Glogger.UseConsoleStrategy()
    #define USE_FILE_LOG_STARATRGY() Glogger.UseFileLogStrategy()
}

#endif